#include "GLCaching.h"
//#include "IDSVisManager.h"
//#include "IDSSkinVis.h"
//#include "IDSMemCheck.h"

using namespace IDS::OpenGL;

//-------------------------------------------------------------------------------------------------
// Test if a bounding box is visible in the current OpenGL view
bool IsBoxVisible(const IDSBox& box)
{
	// Read the OpenGL matrices and viewport
	int viewport[4];
	double modelmatrix[16];
	double projmatrix[16];

	glGetIntegerv(GL_VIEWPORT, viewport);
	glMatrixMode(GL_PROJECTION);
	glGetDoublev(GL_PROJECTION_MATRIX, projmatrix);
	glMatrixMode(GL_MODELVIEW);
	glGetDoublev(GL_MODELVIEW_MATRIX, modelmatrix);

	double xmin = viewport[0]+viewport[2]+1;
	double xmax = viewport[0]-1;
	double ymin = viewport[1]+viewport[3]+1;
	double ymax = viewport[1]-1;

	for (int i = 0; i < 8; i++)
	{
		IDSReal3D p = box.corner(i);
		
		double wpos[3];
		gluProject(
			p[0], p[1], p[2],
			modelmatrix, projmatrix, viewport,
			&wpos[0], &wpos[1], &wpos[2]);

		if (wpos[0] < xmin) xmin = wpos[0];
		if (wpos[0] > xmax) xmax = wpos[0];
		if (wpos[1] < ymin) ymin = wpos[1];
		if (wpos[1] > ymax) ymax = wpos[1];
	}

	if (xmax < viewport[0] ||
		xmin > viewport[0]+viewport[2] ||
		ymax < viewport[1] ||
		ymin > viewport[1]+viewport[3])
	{
		return false;
	}
	else
	{
		return true;
	}
}

//-------------------------------------------------------------------------------------------------
GLCaching::GLCaching()
{
	list = glGenLists(NumLevels);
	compLev = 0;
	//cellColor = IDSColor(rand()%256,rand()%256,rand()%256);
}

//-------------------------------------------------------------------------------------------------
GLCaching::~GLCaching()
{
	if (list)
		glDeleteLists(list, NumLevels);
}

//-------------------------------------------------------------------------------------------------
void GLCaching::refresh(bool onlyColours)
{
	if (compLev && list)
	{
		// If onlyColours is true, refresh only the levels Faces and HighlightFaces
		int maxLev = onlyColours ? Edges : NumLevels;
		unsigned mask = 1;
		for (int i = 0; i < maxLev; i++)
		{
			if (compLev & mask)
			{
				glNewList(list+i, GL_COMPILE);
				glEndList();
			}
			mask <<= 1;
		}
		compLev = 0;
	}
}

//-------------------------------------------------------------------------------------------------
void GLCaching::draw(GLCell* cell, int level)
{
	if (level < 0 || level >= NumLevels)
		return;
	if (!list)
		return;

	// Test if the cell is visible
	if (!cell->elementList.size())
		return;
	if (!IsBoxVisible(cell->boundingBox))
		return;

	if (cell->index == -1)
	{
		// Generate the display list, if needed
		if (!(compLev & (1U << level)))
		{
			glNewList(list+level, GL_COMPILE);
			this->basicDraw(cell, level);
			glEndList();
			compLev |= 1U << level;
		}
		glCallList(list+level);
	}
	else
	{
		// Draw the children cells
		for (int i = 0; i < 2; i++)
		{
			GLCell* child = cell->children[i];
			if (!child->caching)
				child->caching = new GLCaching;
			child->caching->draw(child,level);
		}
	}
}

//-------------------------------------------------------------------------------------------------
// Wrapper class, to call glMaterialfv
struct floatColor {
	float color[4];
	floatColor() { }
	floatColor(unsigned char (&col)[3]) {
		color[0] = col[0] / 255.;
		color[1] = col[1] / 255.;
		color[2] = col[2] / 255.;
		color[3] = 1.;
	}
	floatColor(unsigned char r, unsigned char g, unsigned char b) {
		color[0] = r / 255.;
		color[1] = g / 255.;
		color[2] = b / 255.;
		color[3] = 1.;
	}
	bool equals(const floatColor& fc) const {
		return color[0] == fc.color[0] &&
			   color[1] == fc.color[1] &&
			   color[2] == fc.color[2] &&
			   color[3] == fc.color[3];
	}
	operator const float *() const { return color; }
};

/*
//-------------------------------------------------------------------------------------------------
// Decide the color for the element
floatColor GLCaching::getElemColor(GLElement *elem, IDSVisManager& visMan)
{
	// colorazione selezionati
	if (elem->selected)
		return floatColor(0xEF, 0x00, 0xEF);
	
	if (elem->longTag == ADIACENT_TO_MESH_OBJ)
		return floatColor(visMan.colorVis.colorAdiacentToWireOrTriangle.rgb);

	if (elem->longTag == ADIACENT_SELECTED_MESH_OBJ)
		return floatColor(visMan.colorVis.colorAdiacentToWireOrTriangle.rgb[0]>>1,
						  visMan.colorVis.colorAdiacentToWireOrTriangle.rgb[1]>>1,
						  visMan.colorVis.colorAdiacentToWireOrTriangle.rgb[2]>>1);

	if (elem->longTag == POSITIVE_ANTENNA)
		return floatColor(242,0,0);

	if (elem->longTag == NEGATIVE_ANTENNA)
		return floatColor(0,0,242);

	IDSSkinVis *skin = (IDSSkinVis*)elem->skin;
	int groupid = elem->mesh->getGroup();
	if (!elem->mesh->notReferenced && !visMan.select && visMan.highlighted && visMan.highlighted->type == IDSMeshObject::MESHREF && visMan.highlighted == elem->mesh->meshRef)
	{
		unsigned char* col= skin->meshColor[groupid].rgb;
		return floatColor((col[0]>>1)+127, (col[1]>>1)+127, (col[2]>>1)+127);
	}

	//return floatColor(this->cellColor.rgb);
	return floatColor(skin->meshColor[groupid].rgb);
}

//-------------------------------------------------------------------------------------------------
// Decide the two colors (front and back) for the element
// Assumes that elem->longTag == SHOW_NORMAL_COLOR
void GLCaching::getElemFaceColors(GLElement *elem, IDSVisManager& visMan, floatColor colors[2])
{
	// Colorazione selezionati
	if (elem->selected)
	{
		colors[0] = floatColor(0xEF, 0x00, 0xEF);
		colors[1] = colors[0];
	}

	// Colorazione normale doppia
	else if (elem->doubleFace)
	{
		colors[0] = floatColor(visMan.colorVis.colorMeshDoubleNormal.rgb);
		colors[1] = colors[0];
	}

	// Colorazione normali
	else
	{
		colors[0] = floatColor(visMan.colorVis.colorMeshOutNormal.rgb);
		colors[1] = floatColor(visMan.colorVis.colorMeshInNormal.rgb);
	}
}
*/

//-------------------------------------------------------------------------------------------------
void GLCaching::basicDraw(GLCell* cell, int level)
{
	if( cell->elementList.size() == 0)
		return;

	std::list<GLElement*>::iterator it;
	for( it= cell->elementList.begin(); it != cell->elementList.end(); it++)
	{
		(*it)->basicDraw();
	}
	
	/*IDSVisManager& visMan= IDSVisManager::get();
	switch (level)
	{
	case Faces:
	case HighlightFaces:
	{
		// This variable is used to group triangles and quads
		// together in one glBegin/glEnd block
		int elemSides = 0;

		// Look at the first element to decide if the front/back faces
		// will be colored differently
		bool differentColors = false;
		if (cell->first->longTag == SHOW_NORMAL_COLOR)
		{
			differentColors = true;
			glDisable(GL_COLOR_MATERIAL);
		}
		else
		{
			differentColors = false;
		}
		floatColor colors[2];
		colors[0].color[0] = colors[1].color[0] = -2; // invalid

		for (GLElement* elem = cell->first; elem != cell->last->next; elem = elem->next)
		{
			if (!elem->visible) continue;

			// update the current element type, if necessary
			if (elemSides != elem->nSides)
			{
				if (elemSides != 0)
					glEnd();
				elemSides = elem->nSides;
				glBegin(elemSides == 3 ? GL_TRIANGLES : GL_QUADS);
			}

			// update the current color, if necessary
			if (differentColors)
			{
				floatColor newcolors[2];
				getElemFaceColors(elem, visMan, newcolors);
				if (!newcolors[0].equals(colors[0]))
				{
					glMaterialfv(GL_FRONT, GL_DIFFUSE, newcolors[0]);
					colors[0] = newcolors[0];
				}
				if (!newcolors[1].equals(colors[1]))
				{
					glMaterialfv(GL_BACK, GL_DIFFUSE, newcolors[1]);
					colors[1] = newcolors[1];
				}
			}
			else
			{
				colors[1] = getElemColor(elem, visMan);
				if (!colors[1].equals(colors[0]))
				{
					glColor4fv(colors[1]);
					colors[0] = colors[1];
				}
			}

			// normal
			IDSReal3D n = elem->getNormal();
			glNormal3dv(n.v);

			for (int i = 0; i < elemSides; i++)
				glVertex3dv(elem->vert[i]->coord().v);
		}
		if (elemSides != 0) glEnd();
		if (differentColors) glEnable(GL_COLOR_MATERIAL);
	}
	break;

	// Edges
	// Draw black lines as the element edges
	case Edges:
		glPushAttrib(GL_CURRENT_BIT | GL_LINE_BIT);
		glColor3ub(0x00,0x00,0x00);
		glLineWidth(1);
		for (GLElement* elem = cell->first; elem != cell->last->next; elem = elem->next)
		{
			if (!elem->visible) continue;

			glBegin(GL_LINE_LOOP);
			for (int i = 0; i < elem->nSides; i++)
			{
				glVertex3dv(elem->vert[i]->coord().v);
			}
			glEnd();
		}
		glPopAttrib();
		glCheck;
	break;

	// SelectEdges
	// Draw the element edges in Select mode
	case SelectEdges:
		for (GLElement* elem = cell->first; elem != cell->last->next; elem = elem->next)
		{
			if (!elem->visible) continue;

			idsglPushName(elem);
			glBegin(GL_LINES);
			for (int i = 0; i < elem->nSides; i++)
			{
				int j = (i+1) % elem->nSides;
				glVertex3dv(elem->vert[i]->coord().v);
				glVertex3dv(elem->vert[j]->coord().v);
			}
			glEnd();
			idsglPopName();
		}
		glCheck;
	break;

	// SelectVerts
	// Draw the element vertices in Select mode
	case SelectVerts:
		for (GLElement* elem = cell->first; elem != cell->last->next; elem = elem->next)
		{
			if (!elem->visible) continue;

			for (int i = 0; i < elem->nSides; i++)
			{
				idsglPushName(elem->vert[i]);
				glBegin(GL_POINTS);
				glVertex3dv(elem->vert[i]->coord().v);
				glEnd();
				idsglPopName();
			}
		}
		glCheck;
	break;

	// SelectVerts
	// Draw the elements in Select mode
	case SelectFaces:
		for (GLElement* elem = cell->first; elem != cell->last->next; elem = elem->next)
		{
			if(!elem->visible) continue;

			idsglPushName(elem);
			glBegin(elem->nSides == 3 ? GL_TRIANGLES : GL_QUADS);
			for (int i = 0; i < elem->nSides; i++)
			{
				glVertex3dv(elem->vert[i]->coord().v);
			}
			glEnd();
			idsglPopName();
		}
		glCheck;
	break;
	}*/
}

